
#include <cstdio>
#include <map>
#include <BImage>

#include "ReadWrite_obj.h"

ReadWrite_obj::ReadWrite_obj()
{
	_author = "zym";
	_version = "1.0";
	_description = "This is BWE Plugin for reading *.jpg image file, write width libjpeg, thank a lot !";
}
ReadWrite_obj::~ReadWrite_obj()
{

}

void ReadWrite_obj::setOptions(const BString& options)
{
	_options = options;
}
const BString& ReadWrite_obj::options() const
{
	return _options;
}

BObject* ReadWrite_obj::load(const BString& fileName)
{
	BGroup* group = new BGroup();
	if (!load(fileName, group))
	{
		delete group;
		return 0;
	}
	return group;
}
bool ReadWrite_obj::load(const BString& fileName, BObject* object)
{
	BGroup* group = dynamic_cast<BGroup*>(object);
	BNode* node = dynamic_cast<BNode*>(object);
	if (!group && !node)
		return false;

	if (group)
		group->clear();

	if (node)
		node->clear();

	BFile file(fileName);
	if (!file.open(fileName, IO_Read))
		return false;

	BNodeHolderArray nodes;
	BShapeHolderArray shapes;

	BVectorArray vertices;
	BVectorArray normals;
	BCoordArray coords;

	_fileName = fileName;
	_vs = &vertices;
	_vns = &normals;
	_vts = &coords;

	_node = 0;
	_trigElement = 0;
	_trigPieces = 0;
	_quadElement = 0;
	_quadPieces = 0;
	_mate = 0;

	_mates.clear();
	_geometry = 0;
	_geomName.clear();
	_segment.clear();
	_strIndex.clear();
	_word.clear();

	_geometry = new BGeometry();

	BString mtlfile;
	BString text;
	int line = 0;
	while (file.readLine(text))
	{
		line++;
		text.trimmed();
		if (text.empty())
			continue;
		if (text.beginWith("v "))
		{
			BVector vector;
			if (convertTextToVector(text, 2, vector))
			{
				_vs->append(vector);
			}
			else
			{
				printf("%d Read file failed : %s\n", line, text.cstr());
				break;
			}
			continue;
		}
		if (text.beginWith("vn "))
		{
			BVector normal;
			if (convertTextToVector(text, 3, normal))
			{
				_vns->append(normal);
			}
			else
			{
				printf("%d Read file failed : %s\n", line, text.cstr());
				break;
			}
			continue;
		}
		if (text.beginWith("vt "))
		{
			BCoord coord;
			if (convertTextToCoord(text, 3, coord))
			{
				_vts->append(coord);
			}
			else
			{
				printf("%d Read file failed : %s\n", line, text.cstr());
				break;
			}
			continue;
		}
		if (text.beginWith("f "))
		{
			if (!readFace(text, 2))
			{
				readFace(text, 2);
				printf("Error --- read face failed : %s\n", text.cstr());
				break;
			}
			continue;
		}
		if (text.beginWith("s "))
		{
			BString groupStr;
			text.substr(groupStr, 2);
			int sid = groupStr.toInt();
			continue;
		}
		if (text.beginWith("o "))
		{
			finishGeometry(shapes);

			BString nodeName;
			text.substr(nodeName, 2);
			nodeName.trimmed();
			_node = new BNode();
			_node->setName(nodeName);
			nodes.append(_node);
			continue;
		}
		if (text.beginWith("g "))
		{
			finishGeometry(shapes);

			text.substr(_geomName, 2);
			_geomName.trimmed();
			continue;
		}
		if (text.beginWith("usemtl "))
		{
			finishGeometry(shapes);

			BString mateName;
			text.substr(mateName, 7);
			mateName.trimmed();
			_mate = findMaterial(mateName);
			continue;
		}
		if (text.beginWith("mtllib"))
		{
			text.substr(mtlfile, 6);
			mtlfile.trimmed();
			if (!loadMtllib(mtlfile))
			{
				printf("%d load mtllib failed : %s\n", line, mtlfile.cstr());
			}
			continue;
		}
		if (text[0] == '#')
		{
			continue;
		}
	}

	finishGeometry(shapes);

	for (int i = 0; i < shapes.size(); i++)
	{
		BShape* shape = shapes[i];
		resetOrigin(shape);
	}
	for (int i = 0; i < nodes.size(); i++)
	{
		BNode* node = nodes[i];
		resetOrigin(node);
	}

	BString name = fileName.name();
	name.remove(".obj");

	if (group)
	{
		group->setName(name);
		for (int i = 0; i < shapes.size(); i++)
		{
			BShape* shape = shapes[i];
			group->insert(shape);
		}
		for (int i = 0; i < nodes.size(); i++)
		{
			BNode* node = nodes[i];
			group->insert(node);
		}
	}
	else if (node)
	{
		node->setName(name);
		for (int i = 0; i < shapes.size(); i++)
		{
			BShape* shape = shapes[i];
			node->insertShape(shape);
		}
		for (int i = 0; i < nodes.size(); i++)
		{
			BNode* n = nodes[i];
			for (int i = 0; i < n->shapeCount(); i++)
			{
				BShape* shape = n->shape(i);
				node->insertShape(shape);
			}
		}
	}

	_node = 0;
	_trigElement = 0;
	_trigPieces = 0;
	_quadElement = 0;
	_quadPieces = 0;
	_mate = 0;

	_fileName.clear();
	_mates.clear();
	_geometry = 0;
	_geomName.clear();
	_segment.clear();
	_strIndex.clear();
	_word.clear();
	return true;
}
bool ReadWrite_obj::save(const BString& fileName, const BObject* object) const
{
	return false;
}

BObject* ReadWrite_obj::read(const BBuffer& buffer)
{
	return 0;
}
bool ReadWrite_obj::read(const BBuffer& buffer, BObject* object)
{
	return false;
}
bool ReadWrite_obj::write(BBuffer& buffer, const BObject* object) const
{
	return false;
}

const BString& ReadWrite_obj::author() const
{
	return _author;
}
const BString& ReadWrite_obj::version() const
{
	return _version;
}
const BString& ReadWrite_obj::description() const
{
	return _description;
}
const BString& ReadWrite_obj::message() const
{
	return _message;
}

void ReadWrite_obj::getIllum(int illum)
{
	if (illum == 0)//Color on and Ambient off
		return;
	if (illum == 1)// Color on and Ambient on
		return;
	if (illum == 2)// Highlight on
		return;
	if (illum == 3)// Reflection on and Ray trace on
		return;
	if (illum == 4)// Transparency: Glass on
		return;
	//Reflection : Ray trace on
	if (illum == 5)// Reflection : Fresnel on and Ray trace on
		return;
	if (illum == 6)// Transparency : Refraction on
		return;
	//Reflection : Fresnel off and Ray trace on
	if (illum == 7)// Transparency : Refraction on
		return;
	//Reflection : Fresnel on and Ray trace on
	if (illum == 8)// Reflection on and Ray trace off
		return;
	return;
	if (illum == 9)// Transparency : Glass on
		return;
	//Reflection : Ray trace off
	if (illum == 10)// Casts shadows onto invisible surfaces
		return;
}
bool ReadWrite_obj::convertTextToVector(const BString& text, int pos, BVector& vector)
{
	pos = text.number(_word, pos);
	if (pos == text.size())
		return false;
	vector.x() = _word.toFloat();

	pos = text.number(_word, pos);
	if (pos == text.size())
		return false;
	vector.y() = _word.toFloat();

	pos = text.number(_word, pos);
	if (_word.empty())
		return false;
	vector.z() = _word.toFloat();

	return true;
}
bool ReadWrite_obj::convertTextToCoord(const BString& text, int pos, BCoord& coord)
{
	pos = text.number(_word, pos);
	if (pos == text.size())
		return false;
	coord.x() = _word.toFloat();

	pos = text.number(_word, pos);
	if (_word.empty())
		return false;
	coord.y() = _word.toFloat();

	return true;
}
bool ReadWrite_obj::convertTextToColor(const BString& text, int pos, BColor& color)
{
	pos = text.number(_word, pos);
	if (pos == text.size())
	{
		BByte c = (BByte)(_word.toFloat() * 255);
		color.r() = c;
		color.g() = c;
		color.b() = c;
		return true;
	}
	color.r() = (BByte)(_word.toFloat() * 255);

	pos = text.number(_word, pos);
	if (pos == text.size())
		return false;
	color.g() = (BByte)(_word.toFloat() * 255);

	pos = text.number(_word, pos);
	if (_word.empty())
		return false;
	color.b() = (BByte)(_word.toFloat() * 255);

	return true;
}

bool ReadWrite_obj::readFace(const BString& text, int pos)
{
	static BPieceArray buffer;
	buffer.reset();
	BPiece piece;
	while (pos < text.size())
	{
		int seg_end = text.find(' ', pos);

		text.substr(_segment, pos, seg_end);
		pos = seg_end + 1;

		_segment.trimmed();
		pos = seg_end + 1;
		if (_segment.empty())
			continue;

		int begin = 0;
		int end = 0;

		//vertex index
		end = _segment.find('/', begin);
		if (end == begin)
		{
			printf("Invalid segment : %s", _segment.cstr());
			return false;
		}
		_segment.substr(_strIndex, begin, end);
		piece.v() = _strIndex.toInt() - 1;

		//texcoord index
		begin = end + 1;
		end = _segment.find('/', begin);
		if (begin < end)
		{
			_segment.substr(_strIndex, begin, end);
			piece.vt() = _strIndex.toInt() - 1;
		}

		//normal index
		begin = end + 1;
		end = _segment.find('/', begin);
		if (begin < end)
		{
			_segment.substr(_strIndex, begin, end);
			piece.vn() = _strIndex.toInt() - 1;
		}
		buffer.append(piece);
	}

	if (buffer.size() < 3)
		return false;

	if (buffer.size() == 3)
	{
		if (!_trigElement)
		{
			_trigElement = new BElement(Primitive_Triangles);
			_trigPieces = new BPieceArray();
			_trigElement->setPieces(_trigPieces);
			_geometry->addElement(_trigElement);
		}
		_trigPieces->append(buffer);
		return true;
	}

	if (buffer.size() == 4)
	{
		if (!_quadElement)
		{
			_quadElement = new BElement(Primitive_Quads);
			_quadPieces = new BPieceArray();
			_quadElement->setPieces(_quadPieces);
			_geometry->addElement(_quadElement);
		}
		_quadPieces->append(buffer);
		return true;
	}

	BPieceArray* pieces = new BPieceArray();
	pieces->append(buffer);

	BElement* element = new BElement(Primitive_Polygon);
	element->setPieces(pieces);

	_geometry->addElement(element);

	return true;
}

bool ReadWrite_obj::finishGeometry(BShapeHolderArray& shapes)
{
	if (!_geometry || _geometry->empty())
		return false;

	BVectorArray* vertices = new BVectorArray();
	BVectorArray* normals = new BVectorArray();
	BCoordArray* texCoords = new BCoordArray();

	std::map<int, int> vmap;
	std::map<int, int> vnmap;
	std::map<int, int> vtmap;
	for (int i = 0; i < _geometry->elementCount(); i++)
	{
		BElement* element = _geometry->element(i);
		BPieceArray* pieces = element->pieces();
		for (int pi = 0; pi < pieces->size(); pi++)
		{
			const BPiece& piece = (*pieces)[pi];
			auto vit = vmap.find(piece.v());
			if (vit == vmap.end())
			{
				vertices->append((*_vs)[piece.v()]);
				vmap[piece.v()] = vertices->size() - 1;
			}
			auto vnit = vnmap.find(piece.vn());
			if (vnit == vnmap.end())
			{
				normals->append((*_vns)[piece.vn()]);
				vnmap[piece.vn()] = normals->size() - 1;
			}
			auto vtit = vtmap.find(piece.vt());
			if (vtit == vtmap.end())
			{
				texCoords->append((*_vts)[piece.vt()]);
				vtmap[piece.vt()] = texCoords->size() - 1;
			}
		}
	}
	for (int i = 0; i < _geometry->elementCount(); i++)
	{
		BElement* element = _geometry->element(i);
		BPieceArray* pieces = element->pieces();
		for (int pi = 0; pi < pieces->size(); pi++)
		{
			BPiece& piece = (*pieces)[pi];
			piece.v() = vmap[piece.v()];
			piece.vn() = vnmap[piece.vn()];
			piece.vt() = vtmap[piece.vt()];
		}
	}

	_geometry->setName(_geomName);
	_geometry->setVertices(vertices);
	_geometry->setNormals(normals);
	_geometry->setTexCoords(texCoords);
	if (_mate)
	{
		_geometry->setMaterial(_mate->material);
		_geometry->setTexture(_mate->texture);
		_geometry->setTransparent(_mate->transparent);
	}
	if (_node)
	{
		_node->insertShape(_geometry);
	}
	else
	{
		shapes.append(_geometry.ptr());
	}

	_geometry = new BGeometry();
	_trigElement = 0;
	_trigPieces = 0;
	_quadElement = 0;
	_quadPieces = 0;

	return true;
}

bool ReadWrite_obj::loadMtllib(const BString& mtlfile)
{
	BString mtlFileName = _fileName.path();
	mtlFileName << '/';
	mtlFileName << mtlfile;
	BFile file(mtlFileName);
	if (!file.open(IO_Read))
		return false;

	BByte factor = 255;
	MateObj* mate = 0;
	BString mtlName;
	BString text;
	int line = 0;
	while (file.readLine(text))
	{
		text.trimmed();
		if (text.empty())
			continue;
		if (text[0] == '#')
			continue;
		if (text.beginWith("newmtl "))
		{
			if (!text.substr(mtlName, 6))
				return false;
			mtlName.trimmed();
			mate = new MateObj();
			mate->material = new BMaterial();
			mate->material->setName(mtlName);
			_mates.append(mate);
			continue;
		}
		if (text.beginWith("illum "))
		{
			BString illumStr;
			text.substr(illumStr, 6);
			int illum = illumStr.toInt();
			getIllum(illum);
			continue;
		}
		if (text.beginWith("Ka "))
		{
			BColor ambient = mate->material->ambient();
			ambient.a() = factor;
			if (!convertTextToColor(text, 3, ambient))
				return false;
			mate->material->setAmbient(ambient);
			continue;
		}
		if (text.beginWith("Kd "))
		{
			BColor diffuse = mate->material->diffuse();
			diffuse.a() = factor;
			if (!convertTextToColor(text, 3, diffuse))
				return false;
			mate->material->setDiffuse(diffuse);
			continue;
		}
		if (text.beginWith("Ks "))
		{
			BColor specular = mate->material->specular();
			specular.a() = factor;
			if (!convertTextToColor(text, 3, specular))
				return false;
			mate->material->setSpecular(specular);
			continue;
		}
		if (text.beginWith("Ke "))
		{
			BColor emission = mate->material->emission();
			emission.a() = factor;
			if (!convertTextToColor(text, 3, emission))
				return false;
			mate->material->setEmission(emission);
			continue;
		}
		if (text.beginWith("map_Ka "))
		{
			BString fileName;
			text.substr(fileName, 7);
			fileName.trimmed();
			continue;
		}
		if (text.beginWith("map_Kd "))
		{
			BString fileName;
			text.substr(fileName, 7);
			fileName.trimmed();
			BString absFileName = _fileName.path();
			absFileName << '/' << fileName;
			BImage* image = ReadFile<BImage>(absFileName);
			if (image)
			{
				image->setFileName(fileName);
				image->setBlend(BTexture::Blend_Modulate);
			}
			else
			{
				printf("OBJ Error - read image file failed : %s\n", fileName.cstr());
			}
			mate->texture = image;
			continue;
		}	
		if (text.beginWith("Ns "))
		{
			BString exponentStr;
			text.substr(exponentStr, 3);
			float exponent = exponentStr.toFloat();
			continue;
		}
		if (text.beginWith("Ni "))
		{
			BString pticalStr;
			text.substr(pticalStr, 3);
			float ptical = pticalStr.toFloat();
			continue;
		}
		if (text.beginWith("d "))
		{
			BString factorStr;
			text.substr(factorStr, 2);
			factor = (BByte)(factorStr.toFloat() * 255);
			mate->transparent = factor < 0.99f;
			continue;
		}
		if (text.beginWith("Tr "))
		{
			continue;
		}
		if (text.beginWith("Tf "))
		{
			continue;
		}
	}
	return true;
}
void ReadWrite_obj::resetOrigin(BShape* shape)
{
	BGeometry* geometry = dynamic_cast<BGeometry*>(shape);
	BVectorArray* vertices = geometry->vertices();
	BSpace space = geometry->space();
	BVector center = space.center();
	geometry->setPosition(center);
	for (int i = 0; i < vertices->size(); i++)
	{
		(*vertices)[i] -= center;
	}
	geometry->dirty();
}
void ReadWrite_obj::resetOrigin(BNode* node)
{
	BSpace space;
	if (node->shapeCount() > 0)
	{
		BShape* shape = node->shape(0);
		resetOrigin(shape);
		space = shape->space();
	}
	for (int i = 1; i < node->shapeCount(); i++)
	{
		BGeometry* geometry = (BGeometry*)node->shape(i);
		resetOrigin(geometry);
		space.expand(geometry->space());
	}
	BVector center = space.center();
	for (int i = 0; i < node->shapeCount(); i++)
	{
		BGeometry* geometry = (BGeometry*)node->shape(i);
		geometry->setPosition(geometry->position() - center);
	}
	node->setPosition(center);
}
MateObj* ReadWrite_obj::findMaterial(const BString& matName)
{
	for (BList<MateObjHolder>::iterator it = _mates.begin(); it != _mates.end(); ++it)
	{
		MateObj* mate = it->ptr();
		BMaterial* material = mate->material;
		if (material->name() == matName)
			return mate;
	}
	return 0;
}


